package net.citrite.android;

import com.android.SdkConstants;
import com.android.ide.common.rendering.HardwareConfigHelper;
import com.android.ide.common.rendering.LayoutLibrary;
import com.android.ide.common.rendering.api.*;
import com.android.ide.common.resources.FrameworkResources;
import com.android.ide.common.resources.ResourceItem;
import com.android.ide.common.resources.ResourceRepository;
import com.android.ide.common.resources.ResourceResolver;
import com.android.ide.common.resources.configuration.DeviceConfigHelper;
import com.android.ide.common.resources.configuration.FolderConfiguration;
import com.android.ide.common.resources.configuration.LanguageQualifier;
import com.android.ide.common.resources.configuration.RegionQualifier;
import com.android.io.*;
import com.android.resources.ResourceType;
import com.android.resources.ScreenSize;
import com.android.sdklib.IAndroidTarget;
import com.android.sdklib.devices.Device;
import com.android.sdklib.internal.project.ProjectProperties;
import com.google.common.io.Closeables;
import net.citrite.android.render.RenderFactory;
import net.citrite.android.translator.Translation;
import org.xml.sax.InputSource;
import org.xmlpull.v1.XmlPullParserException;

import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathExpressionException;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;
import java.util.logging.Logger;

import static com.android.SdkConstants.ANDROID_STYLE_RESOURCE_PREFIX;


public class RenderService {
    private final static Logger logger = Logger.getLogger(RenderService.class.getName());
    private IAndroidTarget androidTarget;
    private Device device;
    private LayoutLibrary layoutLibrary;
    private FrameworkResources frameworkResources;
    private Project project = null;
    private String defaultTheme = null;
    private String currentFile;
    private String currentLang;
    private List<MeasureReport> reportList = new ArrayList<>();
    private ProjectCallback projectCallback = new ProjectCallback();
    private SummaryReport summaryReport = new SummaryReport();
    private List<Position> positionList = new ArrayList<>();

    public RenderService(IAndroidTarget target, Device device) {
        this.androidTarget = target;
        this.device = device;
        createFrameResources();
        createLayoutLibrary();
        this.defaultTheme = getDefaultTheme();
    }

    public void render(Project project) throws IOException, XmlPullParserException {
        if (this.androidTarget == null || this.device == null) {
            throw new IllegalArgumentException("target or device is invalid");
        }
        this.project = project;
        File resLocation = project.getResLocation();
        IAbstractFolder folder = new FolderWrapper(resLocation);
        ResourceRepository projectResources = new ResourceRepository(folder, false) {
            @Override
            protected ResourceItem createResourceItem(String s) {
                return new ResourceItem(s);
            }
        };
        projectResources.loadResources();

        // invariant string
        FolderConfiguration invariantConfig = DeviceConfigHelper.getFolderConfig(device.getDefaultState());
        Map<ResourceType, Map<String, ResourceValue>> invariantProjectResMap =
                projectResources.getConfiguredResources(invariantConfig);
        Map<String, ResourceValue> invariantStringMap = invariantProjectResMap.get(ResourceType.STRING);

        // walk the locale of project
        for (String lan : projectResources.getLanguages()) {
            if ("en".equals(lan)) {
                continue;
            }
            String language = lan;
            FolderConfiguration folderConfig = new FolderConfiguration();
            folderConfig.set(invariantConfig);
            folderConfig.setLanguageQualifier(LanguageQualifier.getQualifier(lan));
            for (String reg : projectResources.getRegions(lan)) {
                language += "-" + reg;
                folderConfig.setRegionQualifier(RegionQualifier.getQualifier("r" + reg));
            }
            Map<ResourceType, Map<String, ResourceValue>> localizedProjectResMap =
                    projectResources.getConfiguredResources(folderConfig);
            Map<String, ResourceValue> localizedStringMap = localizedProjectResMap.get(ResourceType.STRING);
            // translate localized string
            Iterator<Map.Entry<String, ResourceValue>> it = invariantStringMap.entrySet().iterator();
            while (it.hasNext()) {
                Map.Entry<String, ResourceValue> item = it.next();
                String key = item.getKey();
                ResourceValue resourceValue = item.getValue();
                if (localizedStringMap.containsKey(key)) {
                    ResourceValue localizedValue = localizedStringMap.get(key);
                    if (resourceValue.getValue().equals(localizedValue.getValue())) {
                        // translate
                        String translation = Translation.Translate(localizedValue.getValue(), language);
                        logger.info(String.format("'%s' is translated into '%s' for language: %s", resourceValue.getValue(), translation, language));
                        localizedValue.setValue(translation);
                    }
                } else {
                    String translation = Translation.Translate(resourceValue.getValue(), language);
                    logger.info(String.format("'%s' is translated into '%s' for language: %s", resourceValue.getValue(), translation, language));
                    ResourceValue localizedValue = new ResourceValue(ResourceType.STRING, key, translation, false);
                    localizedStringMap.put(key, localizedValue);
                }
            }

            // walk layout
            Map<String, ResourceValue> localizedLayoutMap = localizedProjectResMap.get(ResourceType.LAYOUT);
            Iterator<Map.Entry<String, ResourceValue>> layoutIt = localizedLayoutMap.entrySet().iterator();
            while (layoutIt.hasNext()) {
                Map.Entry<String, ResourceValue> item = layoutIt.next();
                ResourceValue resourceValue = item.getValue();
                //String theme = getTheme(resourceValue.getValue());
                ResourceResolver resourceResolver = createResourceResolver(defaultTheme, folderConfig, localizedProjectResMap);

                File layoutFile = new File(resourceValue.getValue());
                XmlParser xmlParser = XmlParser.create(projectCallback, layoutFile);
                HardwareConfigHelper hardwareConfigHelper = new HardwareConfigHelper(device);
                HardwareConfig hardwareConfig = hardwareConfigHelper.getConfig();
                int version = folderConfig.getVersionQualifier().getVersion();
                LayoutLogWrapper layoutLogWrapper = new LayoutLogWrapper(logger);
                SessionParams sessionParams = new SessionParams(xmlParser, SessionParams.RenderingMode.NORMAL, this,
                        hardwareConfig, resourceResolver, projectCallback, version, version, layoutLogWrapper);
                sessionParams.setExtendedViewInfoMode(true);
                RenderSession session = layoutLibrary.createSession(sessionParams);
                Result result = session.getResult();
                if (result.isSuccess() == false) {
                    String msg = result.getErrorMessage();
                    logger.severe(String.format("fail to render <%s>", resourceValue.getValue()));
                    if (msg != null && !msg.isEmpty()) {
                        logger.severe(msg);
                    }
                } else {
                    displayViewObjects(session.getRootViews(), resourceValue.getValue(), language);
                }
            }
        }
        dumpReport();
    }

    private void dumpReport() {
        summaryReport.parse(reportList);
    }

    private String getTheme(String fileName) {
        String theme = defaultTheme;
        InputStream content = null;
        IAbstractFile file = new FileWrapper(fileName);
        try {
            content = file.getContents();
            XPath xpath = XPathBuilder.newXPath();
            String context = xpath.evaluate("//@tools:context", new InputSource(content));
            theme = project.getTheme(context);
            if (theme == null || theme.isEmpty()) {
                theme =  defaultTheme;
            }
        } catch (StreamException e) {
            e.printStackTrace();
        } catch (XPathExpressionException e) {
            e.printStackTrace();
        } finally {
            try {
                Closeables.close(content, true);
            } catch (IOException var11) {
            }
        }
        return theme;
    }

    public ResourceResolver createResourceResolver(String themeStyle, FolderConfiguration config, Map<ResourceType, Map<String, ResourceValue>> resourceMap) {
        boolean isProjectTheme = ResourceHelper.isProjectStyle(themeStyle);
        String themeName = ResourceHelper.styleToTheme(themeStyle);

        Map<ResourceType, Map<String, ResourceValue>> frameworkResourceMap =
                frameworkResources.getConfiguredResources(config);

        ResourceResolver resourceResolver = ResourceResolver.create(resourceMap, frameworkResourceMap,
                themeName, isProjectTheme);
        int apiLevel = androidTarget.getVersion().getApiLevel();
        if (apiLevel >= 21) {
            resourceResolver.setDeviceDefaults("Theme.Material.Light", "Theme.Material");
        } else if (apiLevel >= 14) {
            resourceResolver.setDeviceDefaults("Theme.Holo.Light", "Theme.Holo");
        } else {
            resourceResolver.setDeviceDefaults("Theme.Light", "Theme");
        }
        return resourceResolver;
    }

    private void createFrameResources() {
        String frameResFolder = androidTarget.getPath(IAndroidTarget.RESOURCES);
        LogWrapper logWrapper = new LogWrapper(logger);
        FolderWrapper resFolder = new FolderWrapper(frameResFolder);
        frameworkResources = new FrameworkResources(resFolder);
        frameworkResources.loadResources();
        frameworkResources.loadPublicResources(logWrapper);
    }

    private void createLayoutLibrary() {
        LogWrapper logWrapper = new LogWrapper(logger);
        File layoutLib = androidTarget.getFile(IAndroidTarget.LAYOUT_LIB);
        layoutLibrary = LayoutLibrary.load(layoutLib.getPath(), logWrapper, "Render");
        HashMap<String, Map<String, Integer>> enumMap = new HashMap<String, Map<String, Integer>>();

        FolderConfiguration config = new FolderConfiguration();
        Map<ResourceType, Map<String, ResourceValue>> res = frameworkResources.getConfiguredResources(config);

        // get the ATTR values
        Map<String, ResourceValue> attrItems = res.get(ResourceType.ATTR);
        for (ResourceValue value : attrItems.values()) {
            if (value instanceof AttrResourceValue) {
                AttrResourceValue attr = (AttrResourceValue) value;
                Map<String, Integer> values = attr.getAttributeValues();
                if (values != null) {
                    enumMap.put(attr.getName(), values);
                }
            }
        }

        // get the declare-styleable values
        Map<String, ResourceValue> styleableItems = res.get(ResourceType.DECLARE_STYLEABLE);

        // get the attr from the styleable
        for (ResourceValue value : styleableItems.values()) {
            if (value instanceof DeclareStyleableResourceValue) {
                DeclareStyleableResourceValue dsrc = (DeclareStyleableResourceValue) value;
                Map<String, AttrResourceValue> attrs = dsrc.getAllAttributes();
                if (attrs != null && attrs.size() > 0) {
                    for (AttrResourceValue attr : attrs.values()) {
                        Map<String, Integer> values = attr.getAttributeValues();
                        if (values != null) {
                            enumMap.put(attr.getName(), values);
                        }
                    }
                }
            }
        }
        File fontFolder = androidTarget.getFile(IAndroidTarget.FONTS);
        File buildProp = new File(androidTarget.getLocation(), SdkConstants.FN_BUILD_PROP);
        IAbstractFile buildFile = new FileWrapper(buildProp);
        Map<String, String> builds = ProjectProperties.parsePropertyFile(buildFile, logWrapper);
        LayoutLogWrapper layoutLogWrapper = new LayoutLogWrapper(logger);
        layoutLibrary.init(builds, fontFolder, enumMap, layoutLogWrapper);
    }

    private void displayViewObjects(List<ViewInfo> rootViews, String fileName, String language) {
        logger.info(String.format("Start to analyze the %s layout: %s", language, fileName));
        currentFile = fileName;
        currentLang = language;
        XmlNodeReport.parse(new File(fileName), positionList);
        if (rootViews == null) {
            throw new IllegalArgumentException("View Info List is empty");
        }
        int index = 0;
        for (ViewInfo info : rootViews) {
            index = measure(info, index);
        }
//        projectCallback.clearPosition();
        positionList.clear();
        currentLang = null;
        currentFile = null;
    }

    private int measure(ViewInfo info, int index) {
        summaryReport.inc();
        logger.info(String.format("current control is <%s>", info.getClassName()));
        // display the children
        net.citrite.android.render.Control control = RenderFactory.createRenderControl(info, layoutLibrary);
        if (control != null) {
            Severity severity = control.measure();
            if (!severity.equals(Severity.None)) {
                MeasureReport report = new MeasureReport();
                report.setFile(currentFile);
//                report.setPosition(projectCallback.getPosition(index));
                report.setPosition(positionList.get(index));
                report.addLanguage(currentLang);
                report.setSeverity(severity);
                boolean found = false;
                for (MeasureReport r : reportList) {
                    if (r.equals(report)) {
                        found = true;
                        r.addLanguage(currentLang);
                        break;
                    }
                }
                if (!found) {
                    reportList.add(report);
                }
            }
        }
        List<ViewInfo> children = info.getChildren();
        index ++;
        if (children != null) {
            for (ViewInfo child : children) {
                index = measure(child, index);
            }
        }
        return index;
    }

    private String getDefaultTheme() {
        if (HardwareConfigHelper.isWear(device)) {
            return "@android:style/Theme.DeviceDefault.Light";
        } else if (HardwareConfigHelper.isTv(device)) {
            //noinspection SpellCheckingInspection
            return "@android:style/Theme.DeviceDefault";
        }
        int apiLevel = androidTarget.getVersion().getApiLevel();

        ScreenSize screenSize = device.getDefaultHardware().getScreen().getSize();
        if (apiLevel >= 11 && screenSize == ScreenSize.XLARGE || apiLevel >= 14) {
            return ANDROID_STYLE_RESOURCE_PREFIX + "Theme.Light"; //$NON-NLS-1$
        } else {
            return ANDROID_STYLE_RESOURCE_PREFIX + "Theme"; //$NON-NLS-1$
        }
    }
}
